EKS で Community アドオンが使えるようになっていたので試してみた。metrics-server をアドオン経由でインストール可能に!

EKS で Community アドオンが使えるようになっていたので試してみた。metrics-server をアドオン経由でインストール可能に!

metrics-server の手動セットアップが不要になります!しかも今後対象は増えていきそう!

AWS マネジメントコンソールを確認していた所、作成時のデフォルト設定として最初から metrics-server がインストールされるようになっていました!

スクリーンショット 2025-01-04 12.05.21.png

どうやら、Community アドオンと呼ばれる概念が生まれており、一部 OSS を EKS アドオンと同じように利用できるようになっています(現状は metrics-server のみ)。

You manage community add-ons just like existing Amazon EKS Add-ons. Community add-ons are different from existing add-ons in that they have a unique scope of support.
https://docs.aws.amazon.com/eks/latest/userguide/community-addons.html

Community アドオンとして入れたソフトウェアのフルサポートはしないようです。

Importantly, AWS does not provide full support for community add-ons. AWS supports only lifecycle operations done using AWS APIs, such as installing add-ons or deleting add-ons.
https://docs.aws.amazon.com/eks/latest/userguide/community-addons.html

一方で、インストール時の基本的なサポートは行ってくれます。

Basic Install Support by AWS
https://docs.aws.amazon.com/eks/latest/userguide/eks-add-ons.html

Community アドオン経由で入れた OSS のサポートが必要なら、直接 GitHub のリポジトリで issue を立てたりしてくれと書かれています。
全ての面倒を見るなら 従来の AWS アドオンと変わらないわけで、当然の建付けという所でしょう。

If you require support for a community add-on, utilize the existing project resources. For example, you may create a GitHub issue on the repo for the project.
https://docs.aws.amazon.com/eks/latest/userguide/community-addons.html

ではさっそく試してみます。

アドオンを追加する

今回は Auto Mode を有効化しており、v1.31 の既存 EKS クラスターに追加します。

スクリーンショット 2025-01-04 12.31.55.png

AWS Addon、Marketplace アドオンに加えて、Community add-ons が増えています。

スクリーンショット 2025-01-04 12.33.35.png

metrics-server を選んで追加します。

スクリーンショット 2025-01-04 12.35.11.png

一旦設定はデフォルトとします。

スクリーンショット 2025-01-04 12.35.22.png

Community アドオンでも describe-addon-versions コマンドで、EKS バージョンとの互換性を確認可能です。
使い勝手はこれまでのアドオンと全く同じですね。

% aws eks describe-addon-versions --addon-name metrics-server
{
    "addons": [
        {
            "addonName": "metrics-server",
            "type": "observability",
            "addonVersions": [
                {
                    "addonVersion": "v0.7.2-eksbuild.1",
                    "architecture": [
                        "amd64",
                        "arm64"
                    ],
                    "computeTypes": [
                        "ec2",
                        "hybrid",
                        "fargate",
                        "auto"
                    ],
                    "compatibilities": [
                        {
                            "clusterVersion": "1.31",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        },
                        {
                            "clusterVersion": "1.30",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        },
                        {
                            "clusterVersion": "1.29",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        },
                        {
                            "clusterVersion": "1.28",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        },
                        {
                            "clusterVersion": "1.27",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        },
                        {
                            "clusterVersion": "1.26",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        },
                        {
                            "clusterVersion": "1.25",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        },
                        {
                            "clusterVersion": "1.24",
                            "platformVersions": [
                                "*"
                            ],
                            "defaultVersion": true
                        }
                    ],
                    "requiresConfiguration": false,
                    "requiresIamPermissions": false
                }
            ],
            "publisher": "eks",
            "owner": "community"
        }
    ]
}

特に設定を行わない場合、レプリカ数 2 の deployment として metrics-server が起動されました。

% kubectl describe deployment -n kube-system
Name:                   metrics-server
Namespace:              kube-system
CreationTimestamp:      Sun, 05 Jan 2025 17:03:08 +0900
Labels:                 app.kubernetes.io/instance=metrics-server
                        app.kubernetes.io/managed-by=EKS
                        app.kubernetes.io/name=metrics-server
                        app.kubernetes.io/version=0.7.2
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app.kubernetes.io/instance=metrics-server,app.kubernetes.io/name=metrics-server
Replicas:               2 desired | 2 updated | 2 total | 0 available | 2 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 25% max surge
Pod Template:
  Labels:           app.kubernetes.io/instance=metrics-server
                    app.kubernetes.io/name=metrics-server
  Service Account:  metrics-server
  Containers:
   metrics-server:
    Image:           602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1
    Port:            10250/TCP
    Host Port:       0/TCP
    SeccompProfile:  RuntimeDefault
    Args:
      --secure-port=10250
      --cert-dir=/tmp
      --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
      --kubelet-use-node-status-port
      --metric-resolution=15s
    Limits:
      memory:  400Mi
    Requests:
      cpu:        100m
      memory:     200Mi
    Liveness:     http-get https://:https/livez delay=0s timeout=1s period=10s #success=1 #failure=3
    Readiness:    http-get https://:https/readyz delay=20s timeout=1s period=10s #success=1 #failure=3
    Environment:  <none>
    Mounts:
      /tmp from tmp (rw)
  Volumes:
   tmp:
    Type:                       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:                  <unset>
  Topology Spread Constraints:  topology.kubernetes.io/zone:ScheduleAnyway when max skew 1 is exceeded for selector app.kubernetes.io/instance=metrics-server,app.kubernetes.io/name=metrics-server
  Priority Class Name:          system-cluster-critical
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      False   MinimumReplicasUnavailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:  <none>
NewReplicaSet:   metrics-server-c996b895d (2/2 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  42s   deployment-controller  Scaled up replica set metrics-server-c996b895d to 2

Pod の情報も見てみます。

% kubectl describe pod metrics-server-c996b895d-5vxxr  -n kube-system
Name:                 metrics-server-c996b895d-5vxxr
Namespace:            kube-system
Priority:             2000000000
Priority Class Name:  system-cluster-critical
Service Account:      metrics-server
Node:                 i-008cc696c613aec02/10.0.100.6
Start Time:           Sun, 05 Jan 2025 17:03:44 +0900
Labels:               app.kubernetes.io/instance=metrics-server
                      app.kubernetes.io/name=metrics-server
                      pod-template-hash=c996b895d
Annotations:          <none>
Status:               Running
IP:                   10.0.100.48
IPs:
  IP:           10.0.100.48
Controlled By:  ReplicaSet/metrics-server-c996b895d
Containers:
  metrics-server:
    Container ID:    containerd://99309ed2fb57b4ddad642b49fda7b08c6198aceb7f6a871fbb74922d406fa13a
    Image:           602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1
    Image ID:        602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server@sha256:ddfec9fafbc354a0627dff19826ce257ac656ba3190150c115f177fb40cc4703
    Port:            10250/TCP
    Host Port:       0/TCP
    SeccompProfile:  RuntimeDefault
    Args:
      --secure-port=10250
      --cert-dir=/tmp
      --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
      --kubelet-use-node-status-port
      --metric-resolution=15s
    State:          Running
      Started:      Sun, 05 Jan 2025 17:03:51 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      memory:  400Mi
    Requests:
      cpu:        100m
      memory:     200Mi
    Liveness:     http-get https://:https/livez delay=0s timeout=1s period=10s #success=1 #failure=3
    Readiness:    http-get https://:https/readyz delay=20s timeout=1s period=10s #success=1 #failure=3
    Environment:  <none>
    Mounts:
      /tmp from tmp (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jwfj2 (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  tmp:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
  kube-api-access-jwfj2:
    Type:                     Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:   3607
    ConfigMapName:            kube-root-ca.crt
    ConfigMapOptional:        <nil>
    DownwardAPI:              true
QoS Class:                    Burstable
Node-Selectors:               <none>
Tolerations:                  CriticalAddonsOnly op=Exists
                              node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                              node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Topology Spread Constraints:  topology.kubernetes.io/zone:ScheduleAnyway when max skew 1 is exceeded for selector app.kubernetes.io/instance=metrics-server,app.kubernetes.io/name=metrics-server
Events:
  Type     Reason            Age                    From               Message
  ----     ------            ----                   ----               -------
  Normal   Nominated         2m24s                  karpenter          Pod should schedule on: nodeclaim/system-q79fl
  Warning  FailedScheduling  2m10s (x5 over 2m25s)  default-scheduler  no nodes available to schedule pods
  Warning  FailedScheduling  2m                     default-scheduler  0/2 nodes are available: 2 node(s) had untolerated taint {karpenter.sh/unregistered: }. preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling.
  Normal   Nominated         117s                   karpenter          Pod should schedule on: nodeclaim/system-lw8b2, node/i-008cc696c613aec02
  Normal   Scheduled         110s                   default-scheduler  Successfully assigned kube-system/metrics-server-c996b895d-5vxxr to i-008cc696c613aec02
  Normal   Pulling           109s                   kubelet            Pulling image "602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1"
  Normal   Pulled            103s                   kubelet            Successfully pulled image "602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/metrics-server:v0.7.2-eksbuild.1" in 5.341s (5.341s including waiting). Image size: 17865884 bytes.
  Normal   Created           103s                   kubelet            Created container metrics-server
  Normal   Started           103s                   kubelet            Started container metrics-server

CriticalAddonsOnly の toleration が設定されているため、クラスター運用のために必要な Pod のみを配置する system NodePool 管理のノードに配置されています。

スクリーンショット 2025-01-05 17.10.09.png

この状態で top コマンドを実行すると、ノードの情報を取得できました!

% kubectl top node
NAME                  CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
i-008cc696c613aec02   12m          0%     267Mi           8%
i-083fad0420bccb48d   11m          0%     268Mi           8%

pod の情報も取得できています。

% kubectl top pod -n kube-system
NAME                             CPU(cores)   MEMORY(bytes)
metrics-server-c996b895d-5vxxr   2m           16Mi
metrics-server-c996b895d-dhcpp   2m           16Mi

アドオンの設定を変更する

続いて、アドオンの設定を変更してみます。
アドオンの設定スキーマは下記です(2024/1/5 現在)。

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "additionalProperties": false,
  "definitions":
    {
      "limits":
        {
          "additionalProperties": false,
          "properties":
            { "cpu": { "type": "string" }, "memory": { "type": "string" } },
          "title": "limits",
          "type": "object",
        },
      "resources":
        {
          "additionalProperties": false,
          "properties":
            {
              "limits": { "$ref": "#/definitions/limits" },
              "requests": { "$ref": "#/definitions/limits" },
            },
          "title": "resources",
          "type": "object",
        },
    },
  "description": "Configurable properties for metrics-server",
  "properties":
    {
      "addonResizer":
        {
          "additionalProperties": false,
          "properties":
            {
              "enabled":
                {
                  "default": false,
                  "description": "Whether or not to enable the addon-resizer",
                  "type": "boolean",
                },
              "resources":
                {
                  "$ref": "#/definitions/resources",
                  "description": "Resource requests/limits of the addon resizer container",
                },
            },
          "required": ["enabled"],
          "type": "object",
        },
      "affinity":
        {
          "default":
            {
              "nodeAffinity":
                {
                  "requiredDuringSchedulingIgnoredDuringExecution":
                    {
                      "nodeSelectorTerms":
                        [
                          {
                            "matchExpressions":
                              [
                                {
                                  "key": "kubernetes.io/os",
                                  "operator": "In",
                                  "values": ["linux"],
                                },
                                {
                                  "key": "kubernetes.io/arch",
                                  "operator": "In",
                                  "values": ["amd64", "arm64"],
                                },
                              ],
                          },
                        ],
                    },
                },
              "podAntiAffinity":
                {
                  "preferredDuringSchedulingIgnoredDuringExecution":
                    [
                      {
                        "podAffinityTerm":
                          {
                            "labelSelector":
                              {
                                "matchExpressions":
                                  [
                                    {
                                      "key": "app.kubernetes.io/name",
                                      "operator": "In",
                                      "values": ["metrics-server"],
                                    },
                                  ],
                              },
                            "topologyKey": "kubernetes.io/hostname",
                          },
                        "weight": 100,
                      },
                    ],
                },
            },
          "description": "Affinity of the metrics-server pod",
          "type": ["object", "null"],
        },
      "nodeSelector":
        {
          "description": "Node selector of the metrics-server pod",
          "type": ["object", "null"],
        },
      "podAnnotations":
        {
          "additionalProperties": { "type": "string" },
          "description": "Additional annotations to add to the metrics-server deployment pods",
          "type": "object",
        },
      "podDisruptionBudget":
        {
          "additionalProperties": false,
          "description": "Settings for the PodDisruptionBudget",
          "enabled":
            {
              "default": true,
              "description": "the option to enable managed PDB",
              "type": "boolean",
            },
          "maxUnavailable":
            {
              "anyOf":
                [
                  { "pattern": ".*%$", "type": "string" },
                  { "type": "integer" },
                ],
              "default": 1,
              "description": "maxUnavailable value for managed PDB, can be either string or integer; if it's string, should end with %",
            },
          "minAvailable":
            {
              "anyOf":
                [
                  { "pattern": ".*%$", "type": "string" },
                  { "type": "integer" },
                ],
              "description": "minAvailable value for managed PDB, can be either string or integer; if it's string, should end with %",
            },
          "type": "object",
        },
      "podLabels":
        {
          "additionalProperties": { "type": "string" },
          "description": "Additional labels to add to the metrics-server deployment pods",
          "type": "object",
        },
      "replicas":
        {
          "default": 2,
          "description": "Number of replicas in the metrics-server deployment",
          "minimum": 1,
          "type": "integer",
        },
      "resources":
        {
          "$ref": "#/definitions/resources",
          "description": "Resource requests/limits of the metrics-server container",
        },
      "tolerations":
        {
          "additionalProperties": false,
          "default": [{ "key": "CriticalAddonsOnly", "operator": "Exists" }],
          "description": "Tolerations of the metrics-server pod",
          "items": { "type": "object" },
          "type": "array",
        },
      "topologySpreadConstraints":
        {
          "default":
            [
              {
                "labelSelector":
                  {
                    "matchLabels":
                      {
                        "app.kubernetes.io/instance": "metrics-server",
                        "app.kubernetes.io/name": "metrics-server",
                      },
                  },
                "maxSkew": 1,
                "topologyKey": "topology.kubernetes.io/zone",
                "whenUnsatisfiable": "ScheduleAnyway",
              },
            ],
          "description": "The coredns pod topology spread constraints",
          "type": "array",
        },
    },
  "type": "object",
}

本番環境ではクラスターの動作に必要な Pod を専用ノードに配置するべきかもしれませんが、検証用クラスターでは過剰に思います。

https://docs.aws.amazon.com/eks/latest/userguide/critical-workload.html

また、冗長化も不要なため、下記設定に変更します。

{
  "replicas": 1,
  "tolerations": []
}

スクリーンショット 2025-01-05 17.27.24.png

Pod 数も減って、general-purpose NodePool で管理されたインスタンスのみが存在する形になりました。

% kubectl get pod -n kube-system
NAME                            READY   STATUS    RESTARTS   AGE
metrics-server-9f6cbf79-g5z72   1/1     Running   0          7m32s

スクリーンショット 2025-01-05 17.28.22.png

設定が良い感じに反映されてますね!

Horizontal Pod Autoscaler(HPA) を利用してみる

まず、Nginx をデプロイします。

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: nginx
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      containers:
        - image: nginx:1.14.2
          imagePullPolicy: Always
          name: nginx
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "0.5"

この状態では、Pod が一つだけ存在します。

% kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
nginx-64549c5599-hk8h2   1/1     Running   0          12s

HPA を設定します。

% kubectl autoscale deployment nginx --cpu-percent=50 --min=1 --max=5
horizontalpodautoscaler.autoscaling/nginx autoscaled

まだ Pod が一つだけ存在する状態です。

% kubectl get hpa
NAME    REFERENCE          TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
nginx   Deployment/nginx   cpu: 0%/50%   1         5         1          24s

Pod に乗り込んで yes > /dev/null で CPU 負荷をかけます。

% kubectl exec -it nginx-64549c5599-hk8h2 -- /bin/bash
root@nginx-64549c5599-hk8h2:/# yes > /dev/null

すぐに Pod がスケールアウトしました(特に設定していないので、15 秒間隔でスケールアウトするかの判断が発生するはずです)。

% kubectl get pod
NAME                     READY   STATUS              RESTARTS   AGE
nginx-64549c5599-bcbfk   1/1     Running             0          40s
nginx-64549c5599-hk8h2   1/1     Running             0          5m48s
nginx-64549c5599-jlwrg   0/1     ContainerCreating   0          40s
nginx-64549c5599-k5gkf   1/1     Running             0          40s

CPU 負荷が上がっている様子も確認できます。

% kubectl top pod
NAME                     CPU(cores)   MEMORY(bytes)
nginx-64549c5599-bcbfk   0m           1Mi
nginx-64549c5599-hk8h2   998m         2Mi
nginx-64549c5599-jlwrg   2m           1Mi
nginx-64549c5599-k5gkf   0m           1Mi

合わせて Node 側もスケーリングしています。
Pod が一つだけ ContainerCreating で止まっていたのは Node のプロビジョニングが走ったからですね。

% kubectl get node
NAME                  STATUS   ROLES    AGE   VERSION
i-00c6beb080e200030   Ready    <none>   83s   v1.31.1-eks-1b3e656
i-03528a7559f9a331b   Ready    <none>   29m   v1.31.1-eks-1b3e656

metrics-server アドオン追加時にプロビジョニングされた c5a.large は 2vCPU のインスタンスです。
0.5vCPU を要求する Nginx の Pod が 3 つと 0.1 vCPU を要求する metrics-server でほぼいっぱいなので、次の Nginx の Pod を作成する前にノードのスケールが走った形だと思います。
※ 0.5×3+0.1=1.6(vCPU) を使われているので、次の Nginx(0.5vCPU) が起動できない。

Ctl + C で yes コマンドを停止すると、5 分程度でスケールインしました(こちらも特に設定していないので、5 分間隔でスケールインするかの判断が発生するはずです)。

% kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
nginx-64549c5599-hk8h2   1/1     Running   0          19m

Node も不要になったので、合わせてスケールインしてますね。

% kubectl get node
NAME                  STATUS   ROLES    AGE   VERSION
i-00c6beb080e200030   Ready    <none>   17m   v1.31.1-eks-1b3e656

まとめ

metrics-server をアドオンとして扱えるようにして欲しいというのは、containers-roadmap に 2019 年から存在して 3 桁のリアクションがつくような、多くの人に望まれた機能でした。

https://github.com/aws/containers-roadmap/issues/261

Community アドオンという新しい概念とともに対応されたのはめちゃくちゃ嬉しいです。
サポート範囲が限定的とはいえ、インストール時の面倒は見てくれるそうなので、上手く使えば大きく工数を減らすことが可能です。
現時点では metrics-server だけですが、他 OSS への拡張も期待できます。
metrics-server と同様に kubernetes-sigs リポジトリで管理されている external-dns 辺りにも拡張されると嬉しいなと思いました。
後は Argo CD 辺りもこの枠組みで対応してくれたら嬉しいですね。

https://github.com/aws/containers-roadmap/issues/1176

EKS セットアップ時の負荷がどんどん減っててすごい!!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.